package lib

import (
	"context"
	"math/rand"
	"sync"
	"time"

	"github.com/go-redis/redis/v8"
)

var initOnce = sync.Once{}

func RedisConnFactory() *redis.Client {
	initOnce.Do(func() {
		if ConfRedisMap != nil && ConfRedisMap.List != nil {
			sequence := 0
			RedisMapPool = map[int]*redis.Client{}
			for _, cfg := range ConfRedisMap.List {
				for _, randHost := range cfg.ProxyList {
					if cfg.ConnTimeout == 0 {
						cfg.ConnTimeout = 150
					}
					if cfg.ReadTimeout == 0 {
						cfg.ReadTimeout = 1000
					}
					if cfg.WriteTimeout == 0 {
						cfg.WriteTimeout = 1000
					}

					rdb := redis.NewClient(&redis.Options{
						Addr:         randHost,
						Password:     cfg.Password, // no password set
						DB:           cfg.Db,       // use default DB
						DialTimeout:  time.Duration(cfg.ConnTimeout) * time.Millisecond,
						ReadTimeout:  time.Duration(cfg.ReadTimeout) * time.Millisecond,
						WriteTimeout: time.Duration(cfg.WriteTimeout) * time.Millisecond,
					})

					rdb.AddHook(redisHook{})

					RedisMapPool[sequence] = rdb
					sequence++
				}
			}
		}
	})
	return RedisMapPool[rand.Intn(len(ConfRedisMap.List))]
}

func CloseRedis() {
	for _, rdb := range RedisMapPool {
		rdb.Close()
	}
}

type redisHook struct{}

var _ redis.Hook = redisHook{}

var begin time.Time

func (redisHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
	begin = time.Now()
	return ctx, nil
}

func (redisHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
	source := FileWithLineNum()
	trace := GetTraceContext(ctx)
	elapsed := time.Since(begin)
	messages := map[string]interface{}{"errMsg": cmd.Err(), "cmd": cmd.Args(), "source": source, "current_time": NowFunc(),
		"elapsed": float64(elapsed.Nanoseconds()) / 1e6}
	switch {
	case cmd.Err() != nil:
		Log.TagError(trace, "_com_redis_error", messages)
	default:
		Log.TagDebug(trace, "_com_redis_info", messages)
	}
	return nil
}

func (redisHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
	begin = time.Now()
	return ctx, nil
}

func (redisHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
	source := FileWithLineNum()
	trace := GetTraceContext(ctx)
	elapsed := time.Since(begin)
	for _, cmd := range cmds {
		messages := map[string]interface{}{"errMsg": cmd.Err(), "cmd": cmd.Args(), "source": source, "current_time": NowFunc(),
			"elapsed": float64(elapsed.Nanoseconds()) / 1e6}
		switch {
		case cmd.Err() != nil:
			Log.TagError(trace, "_com_redis_error", messages)
		default:
			Log.TagDebug(trace, "_com_redis_info", messages)
		}
	}
	return nil
}
